﻿using System;
using System.Threading;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
using Rhino.Mocks;
using SimpleServiceBus.Bus;
using SimpleServiceBus.Bus.MessageManagement;
using SimpleServiceBus.Bus.MessageManagement.Callbacks;
using SimpleServiceBus.Bus.Pipeline;
using SimpleServiceBus.Bus.Subscriptions;
using SimpleServiceBus.Bus.Transport;
using SimpleServiceBus.Endpoint;
using SimpleServiceBus.Messages.ControlMessages;
using SimpleServiceBus.Messages.Subscriptions;
using SimpleServiceBus.Tests.Stubs;

namespace SimpleServiceBus.Tests.Bus
{
    [TestFixture]
    public class MessageBusTestFixture
    {
        [Test]
        public void Can_Start_And_Stop()
        {
            TestableBus bus = CreateBus();
            RegisterStubService<ISubscriptionManager>(bus);

            bus.Start();
            bus.Stop();

          
        }

        [Test]
        public void Publish_Will_Delegate_To_Subscription_Manager()
        {
            var bus = CreateBus();
            var submgr = RegisterStubService<ISubscriptionManager>(bus);
            
            const string message = "message";

            submgr.Stub(action => action.Publish(message));

            bus.Publish(message);

            submgr.AssertWasCalled(action => action.Publish(message));
        }

        [Test]
        public void Subscribe_Will_Resolve_Destination_And_Send_Subscription_Request()
        {
            var bus = CreateBus();
            var msgmgr = RegisterStubService<IMessageTypeManagementService>(bus);

            bus.Start();
            try
            {
                msgmgr.Stub(action => action.GetDestinationFor<string>()).Return("destination");

              bus.Subscribe<string>();

                msgmgr.AssertWasCalled(action => action.GetDestinationFor<string>());
                Assert.That(bus.Transport.SentMessages.Count, Is.EqualTo(1));
                Assert.That(bus.Transport.SentMessages[0].Recipients[0],Is.EqualTo("destination"));

                var request = (SubscribeToMessageRequest)bus.Transport.SentMessages[0].Message.Body;

                Assert.That(request.MessageType, Is.EqualTo(typeof (string).AssemblyQualifiedName));
                Assert.That(request.SubscriberAddress, Is.EqualTo(bus.Transport.ReturnAddress));
            }
            finally
            {
                bus.Stop();
            }
        }

        [Test][ExpectedException(typeof(Exception))]
        public void Subscribing_To_Message_Without_Known_Destination_Will_Throw_Exception()
        {
            var bus = CreateBus();
            var msgmgr = RegisterStubService<IMessageTypeManagementService>(bus);
        
            bus.Start();
            try
            {
                msgmgr.Stub(action => action.GetDestinationFor<string>()).Return(String.Empty);

               
                bus.Subscribe<string>();
            }
            finally
            {
                bus.Stop();
            }
        }

        [Test]
        public void Unsubscribe_Will_Resolve_Destination_And_Send_Unsubscribe_Request()
        {
            var bus = CreateBus();
            var msgmgr = RegisterStubService<IMessageTypeManagementService>(bus);

            bus.Start();
            try
            {
                msgmgr.Stub(action => action.GetDestinationFor<string>()).Return("destination");

              
                bus.Unsubscribe<string>();

                msgmgr.AssertWasCalled(action => action.GetDestinationFor<string>());
                Assert.That(bus.Transport.SentMessages.Count, Is.EqualTo(1));
                Assert.That(bus.Transport.SentMessages[0].Recipients[0], Is.EqualTo("destination"));

                var request = (UnsubscribeFromMessageRequest)bus.Transport.SentMessages[0].Message.Body;

                Assert.That(request.MessageType, Is.EqualTo(typeof(string).AssemblyQualifiedName));
                Assert.That(request.SubscriberAddress, Is.EqualTo(bus.Transport.ReturnAddress));
            }
            finally
            {
                bus.Stop();
            }
        }

        [Test]
        [ExpectedException(typeof(Exception))]
        public void Unsubscribing_From_Message_Without_Known_Destination_Will_Throw_Exception()
        {
            var bus = CreateBus();
            var msgmgr = RegisterStubService<IMessageTypeManagementService>(bus);

            bus.Start();
            try
            {
                msgmgr.Stub(action => action.GetDestinationFor<string>()).Return(String.Empty);
                bus.Unsubscribe<string>();
            }
            finally
            {
                bus.Stop();
            }
        }

        [Test]
        public void Send_Will_Resolve_Destination()
        {
            var bus = CreateBus();
            var msgmgr = RegisterStubService<IMessageTypeManagementService>(bus);

            var message = "message";

            bus.Start();
            try
            {
                msgmgr.Stub(action => action.GetDestinationForMessage(message)).Return("destination");

               
                bus.Send(message);

                msgmgr.AssertWasCalled(action => action.GetDestinationForMessage(message));
                Assert.That(bus.Transport.SentMessages.Count, Is.EqualTo(1));
                Assert.That(bus.Transport.SentMessages[0].Recipients[0], Is.EqualTo("destination"));

                var request = (string)bus.Transport.SentMessages[0].Message.Body;
                Assert.That(request, Is.EqualTo(message));
            }
            finally
            {
                bus.Stop();
            }
        }

        [Test][ExpectedException(typeof(Exception))]
        public void Send_Message_Without_Known_Destination_Will_Throw_Exception()
        {
            var bus = CreateBus();
            var msgmgr = RegisterStubService<IMessageTypeManagementService>(bus);

            var message = "message";

            bus.Start();
            try
            {
                msgmgr.Stub(action => action.GetDestinationForMessage(message)).Return(null);
                bus.Send(message);
            }
            finally
            {
                bus.Stop();
            }
        }

        [Test]
        public void Send_Will_Execute_Pipeline_Give_Message_To_Transport_And_Register_Callback()
        {
            var bus = CreateBus();
            var plFactory = RegisterStubService<IMessagePipelineFactory>(bus);
            var cbManager = RegisterStubService<ICorrelatedCallbackManager>(bus);
            var pipeline = MockRepository.GenerateStub<IMessagePipeline>();
            var context = MockRepository.GenerateStub<IMessageContext>();

            bus.DummyEnvelopeToSend = new MessageEnvelope{MessageID = "testid"};
            
            var message = "message";

            bus.Start();
            try
            {
                pipeline.Stub(p => p.CurrentContext).Return(context);

                plFactory.Stub(action => action.CreatePipelineFor(bus.DummyEnvelopeToSend, PipelineDirection.Send)).Return(pipeline);
                cbManager.Stub(action => action.Register(bus.DummyEnvelopeToSend.MessageID)).Return(
                    new Callback(bus.DummyEnvelopeToSend.MessageID));

                ICallback cb = bus.Send(message, "destination");
                
                plFactory.AssertWasCalled(action => action.CreatePipelineFor(bus.DummyEnvelopeToSend, PipelineDirection.Send));
                pipeline.AssertWasCalled(action => action.ProcessMessage());
                cbManager.AssertWasCalled(action => action.Register(bus.DummyEnvelopeToSend.MessageID));

                Assert.That(bus.Transport.SentMessages[0].Message, Is.SameAs(bus.DummyEnvelopeToSend));
                Assert.That(bus.Transport.SentMessages[0].Recipients[0], Is.EqualTo("destination"));

                Assert.That(((Callback) cb).MessageId, Is.SameAs(bus.DummyEnvelopeToSend.MessageID));
            }
            finally
            {
                bus.Stop();
            }
        }

        [Test]
        public void Send_Local_Will_Ask_Transport_To_Receive_Later_With_No_Distributor_Configured()
        {
            var bus = CreateBus();
            ((TestTransport) bus.ServiceManager.GetService<ITransport>()).StartWorkerThreads = false;

            bus.Start();
            try
            {
                bus.Transport.ToReceive.Clear();
                bus.SendLocal("test");
                Assert.That(bus.Transport.ToReceive.Count, Is.EqualTo(1));
                Assert.That(bus.Transport.ToReceive.Peek().Body, Is.EqualTo("test"));
            }
            finally
            {
                bus.Stop();
            }

        }

        [Test]
        public void Send_Local_Will_Send_To_Distributor_When_Configured()
        {
            var bus = CreateBus();
            bus.DistributorDataAddress = "notnull";

            bus.Start();
            try
            {

                bus.SendLocal("test");
                Assert.That(bus.Transport.SentMessages.Count, Is.EqualTo(1));
                Assert.That(bus.Transport.ToReceive.Count, Is.EqualTo(0));

                Assert.That(bus.Transport.SentMessages[0].Recipients[0], Is.EqualTo(bus.DistributorDataAddress));
            }
            finally
            {
                bus.Stop();
            }
        }

        [Test]
        public void Reply_Will_Send_Correlated_Message_To_Sender_Of_Current_Message()
        {
            var bus = CreateBus();
            var rec = new MessageEnvelope
                          {
                              ReturnAddress = "BabyComeBackToMe",
                              MessageID = "TheIdYouLoveToHate"
                          };

            bus.DummyEnvelopeBeingReceived = rec;
           

            bus.Start();
            try
            {

                bus.Reply("Hey, thanks for nothing buddy...");

                MessageEnvelope sent = bus.Transport.SentMessages[0].Message;

                Assert.That(bus.Transport.SentMessages.Count, Is.EqualTo(1));
                Assert.That(sent.CorrelationID,Is.EqualTo(rec.MessageID));
                Assert.That(bus.Transport.SentMessages[0].Recipients[0], Is.EqualTo("BabyComeBackToMe"));
                Assert.That(sent.Body, Is.EqualTo("Hey, thanks for nothing buddy..."));
            }
            finally
            {
                bus.Stop();
            }
        }

        [Test]
        public void Forward_Will_Pass_Message_Directly_To_Transport()
        {
            var bus = CreateBus();
            var message = new MessageEnvelope{Body = "hi", MessageID = "ho"};

            bus.Start();
            try
            {
                bus.Forward(message, "Tag, you're it");

                Assert.That(bus.Transport.SentMessages[0].Message, Is.SameAs(message));
                Assert.That(bus.Transport.SentMessages[0].Recipients[0], Is.EqualTo("Tag, you're it"));

            }
            finally
            {
                bus.Stop();
            }
        }

        [Test]
        public void When_Message_Received_From_Transport_Will_Handle_Message_And_Send_Ready_Message()
        {
            var bus = CreateBus();
            bus.StubHandleMessage = true;
            bus.DistributorControlAddress = "notnull";//so ready message will be sent
            
            var msg = MockRepository.GenerateStub<MessageEnvelope>();
            msg.Stub(action => action.IsInitializationMessageFor(bus)).Return(false);

            bus.Start();
            try
            {
                bus.PublicAcceptMessageFromTransport(this, new MessageReceivedEventArgs(msg));

                Assert.That(bus.WasMessageHandled, Is.True);

                Assert.That(bus.Transport.SentMessages.Count, Is.EqualTo(1));
                Assert.That(bus.Transport.SentMessages[0].Message.Body, Is.TypeOf(typeof (ReadyMessage)));

            }
            finally
            {
                bus.Stop();
            }
        }

        [Test]
        public void When_Message_Received_From_Transport_Will_Discard_Init_Message()
        {
            var bus = CreateBus();
            var msg = MockRepository.GenerateStub<MessageEnvelope>();
            msg.Stub(action => action.IsInitializationMessageFor(bus)).Return(true);

            bus.Start();
            try
            {
                bus.PublicAcceptMessageFromTransport(this,new MessageReceivedEventArgs(msg));
                Assert.IsNull(bus.Transport.SentMessages);
            }
            finally
            {
                bus.Stop();
            }
                
        }

        [Test]
        public void HandleMessage_Will_Execute_Pipeline()
        {
            var bus = CreateBus();
            var plFactory = RegisterStubService<IMessagePipelineFactory>(bus);
            var pipeline = MockRepository.GenerateStub<IMessagePipeline>();
            var events = new PipelineEvents();
            pipeline.Stub(p => p.PipelineEvents).Return(events);

            var msg = new MessageEnvelope();

            plFactory.Stub(action => action.CreatePipelineFor(msg, PipelineDirection.Receive)).
                Return(pipeline);

            bus.Start();
            try
            {
                bus.PublicHandleMessage(msg);

                plFactory.AssertWasCalled(action => action.CreatePipelineFor(msg, PipelineDirection.Receive));
                pipeline.AssertWasCalled(action=>action.ProcessMessage());
            }
            finally
            {
                bus.Stop();
            }
        }

        [Test]
        public void GetMessageEnvelopeFor_Propertly_Initializes_Envelope_From_Message()
        {
            var bus = CreateBus();
            bus.Start();
            try
            {
                var env = bus.PublicGetMessageEnvelopeFor(new TestBusMessage(), "forme?");

                Assert.That(env.Body, Is.TypeOf(typeof(TestBusMessage)));
                Assert.That(env.ReturnAddress, Is.EqualTo(bus.PublicGetReturnAddressFor("forme?")));

                Assert.That(env.Recoverable, Is.True);
                //Assert.That(env.TimeToBeReceived, Is.EqualTo(TimeSpan.Parse("00:00:05")));

                //This time, no attributes
                env = bus.PublicGetMessageEnvelopeFor(new TestBusMessageWithoutAttributes(), "forme?");

                Assert.That(env.Recoverable, Is.True);
                //Assert.That(env.TimeToBeReceived, Is.EqualTo(TimeSpan.MaxValue));
                
            }
            finally
            {
                bus.Stop();
            }
        }

        [Test]
        public void GetReturnAddressFor_Returns_Transport_Address_When_No_Distributor()
        {
            var bus = CreateBus();

            bus.Start();
            try
            {
                Assert.That(bus.PublicGetReturnAddressFor("whoever"),
                            Is.EqualTo(bus.Transport.ReturnAddress));
            }
            finally
            {
                bus.Stop();
            }
        }

        [Test]
        public void GetReturnAddressFor_Returns_Distributor_When_Configured()
        {
            var bus = CreateBus();
            bus.DistributorDataAddress = "hello";
            bus.DistributorControlAddress = "goodbye";

            bus.Start();
            try
            {
                Assert.That(bus.PublicGetReturnAddressFor("whoever"),
                            Is.EqualTo(bus.DistributorDataAddress));
            }
            finally
            {
                bus.Stop();
            }
        }

        [Test]
        public void GetReturnAddrssFor_Returns_Transport_Address_When_Sent_To_Distributor_Control_Bus()
        {
            var bus = CreateBus();
            bus.DistributorDataAddress = "hello";
            bus.DistributorControlAddress = "goodbye";

            bus.Start();
            try
            {
                Assert.That(bus.PublicGetReturnAddressFor("goodbye"),
                            Is.EqualTo(bus.Transport.ReturnAddress));
            }
            finally
            {
                bus.Stop();
            }
        }

        [Test]
        public void GetReturnAddressFor_Respects_PropegateReturnAddressOnSent()
        {
            var bus = CreateBus();
            bus.DistributorDataAddress = "hereiam";
            bus.DistributorControlAddress = "thereigo";
            bus.PropogateReturnAddressOnSend = true;

            bus.Start();
            try
            {
                Assert.That(bus.PublicGetReturnAddressFor(bus.DistributorDataAddress),
                            Is.EqualTo(bus.Transport.ReturnAddress));
            }
            finally
            {
                bus.Stop();
            }
        }

        
        private static TestableBus CreateBus()
        {
            var ep = MockRepository.GenerateStub<IMessagingEndpoint>();
            var svcs = new EndpointServiceManager(ep);

            var transport = new TestTransport();
            svcs.RegisterService<ITransport>(transport);

            return new TestableBus(svcs) { Endpoint = ep };
        }

        private static T RegisterStubService<T>(EndpointServiceBase bus)
            where T:class
        {
            var stub = MockRepository.GenerateStub<T>();
            bus.ServiceManager.RegisterService<T>(stub);
            return stub;
        }


    }

    [Serializable]
    [TimeToBeReceived("00:00:05")]
    [Recoverable]
    public class TestBusMessage
    {
        
    }

    [Serializable]
    public class TestBusMessageWithoutAttributes
    {
        
    }

    public class TestableBus : MessageBus
    {
        private readonly EndpointServiceManager _services;

        public TestableBus(EndpointServiceManager services)
        {
            _services = services;
        }

        public override EndpointServiceManager ServiceManager
        {
            get
            {
                return _services;
            }
        }

        public new TestTransport Transport {get { return (TestTransport) base.Transport;}}

        public MessageEnvelope DummyEnvelopeToSend;
        public MessageEnvelope DummyEnvelopeBeingReceived;

        protected override MessageEnvelope GetMessageEnvelopeFor(object message, string[] destinations)
        {
            if (DummyEnvelopeToSend != null)
            {
                DummyEnvelopeToSend.Body = message;
                return DummyEnvelopeToSend;
            }
            return base.GetMessageEnvelopeFor(message, destinations);

        }

        public override MessageEnvelope MessageCurrentlyBeingReceived
        {
            get
            {
                return DummyEnvelopeBeingReceived ?? base.MessageCurrentlyBeingReceived;
            }
        }

        public bool WasMessageHandled;
        public bool StubHandleMessage;

        protected override void HandleMessage(MessageEnvelope m)
        {
            if (StubHandleMessage)
            {
                WasMessageHandled = true;
                return;
            }
            base.HandleMessage(m);
        }

        public void PublicHandleMessage(MessageEnvelope message)
        {
            base.HandleMessage(message);
        }

        public void PublicAcceptMessageFromTransport(object sender,MessageReceivedEventArgs args)
        {
            base.AcceptMessageFromTransport(sender, args);
        }

        public MessageEnvelope PublicGetMessageEnvelopeFor(object message, params string[] destinations)
        {
            return base.GetMessageEnvelopeFor(message, destinations);
        }

        public string PublicGetReturnAddressFor(params string[] destinations)
        {
            return base.GetReturnAddressFor(destinations);
        }
    }
}